<?php


namespace App\Traits;

use App\Exceptions\ApiException;
use App\Helpers\LogHelper;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Exception\RequestException;
use Psr\Http\Message\ResponseInterface;
use Throwable;


/**
 * Trait GuzzleHttpTrait
 *
 * @tips    在使用的类内 use GuzzleHttpTrait
 *
 * @package App\Traits
 */
trait GuzzleHttpTrait
{
	/**
	 * 链接对象
	 *
	 * @var Client
	 */
	private Client $client;
	/**
	 * HTTP 链接超时
	 *
	 * @var int
	 */
	private $timeout = 5;

	/**
	 * 重新初始化HTTP链接
	 *
	 * @var bool
	 */
	private bool $reload = false;

	/**
	 * 设置超时时间
	 *
	 * @param int $timeout
	 */
	public function setTimeout(int $timeout)
	{
		$this->timeout = $timeout;
	}

	/**
	 * 设置是否重连超时时间，每次加载后，需要重新设置
	 *
	 * @param bool $reload
	 */
	public function setReload(bool $reload)
	{
		$this->reload = $reload;
	}

	/**
	 * Guzzle Http Client
	 *
	 * @return Client
	 */
	private function getClient(): Client
	{
		if ($this->reload || $this->client == null) {
			$this->client = new Client();

			$this->reload = false;
		}

		return $this->client;
	}

	/**
	 * 处理Response返回的数据
	 *
	 * @param ResponseInterface $response
	 * @param bool              $origin
	 * @return mixed|string
	 * @throws ApiException
	 */
	private function getResponseData(ResponseInterface $response, bool $origin = false)
	{
		$statusCode = $response->getStatusCode();
		if ($statusCode != 200) {
			$msgArr = [
				'statusCode' => $statusCode,
				'reason'     => $response->getReasonPhrase(),
			];
			throw new ApiException(json_encode($msgArr), ApiException::CODE_2029);

		}
		$body = $response->getBody()->getContents();

		return $origin ? $body : json_decode($body, true);
	}

	/**
	 * get请求
	 *
	 * @param string $url    请求地址
	 * @param array  $query  query参数
	 * @param bool   $origin 返回数据是否需要json_decode
	 * @return mixed|array
	 * @throws ApiException
	 */
	public function guzzleHttpGet(string $url, array $query = [], bool $origin = false)
	{
		$this->getClient();
		$options = [];
		if ($query) {
			$options['query'] = $query;
		}

		return $this->httpClient('GET', $url, $options, $origin);
	}


	/**
	 * post请求
	 *
	 * @param string $url    请求地址
	 * @param array  $params 请求参数
	 * @param bool   $origin 返回数据是否需要json_decode
	 * @return mixed|array
	 * @throws ApiException
	 */
	public function guzzleHttpPost(string $url, array $params = [], bool $origin = false)
	{
		self::getClient();

		if (empty($params['headers'])) {
			$params['headers'] = [
				'Content-type' => 'application/json',
			];
		}

		return $this->httpClient('POST', $url, $params, $origin);
	}

    /**
     * 请求
     *
     * @param string $verb    请求方法
     * @param string $url     请求地址
     * @param array  $options 请求参数
     * @param false  $origin  返回数据是否需要json_decode
     * @return mixed|string|void
     * @throws ApiException
     */
	private function httpClient(string $verb, string $url, array $options = [], bool $origin = false)
	{
		if (empty($options['timeout'])) {
			$options['timeout'] = $this->timeout;
		}

		try {
			// 请求获取响应
			$optionsForLog = $options;
			if (isset($optionsForLog["json"]["works_data"])) {
				$optionsForLog["json"] = array_merge($optionsForLog["json"], ['works_data' => ""]);
			}
			LogHelper::info('guzzle', ['start', $verb, $url, $optionsForLog, $origin]);

			$response = $this->client->request($verb, $url, $options);
			// 加工数据
			$res = $this->getResponseData($response, $origin);

			is_array($res) && LogHelper::info('guzzle', ['end', $res]);
			return $res;
		} catch (RequestException|GuzzleException $ex) {
			$this->logGuzzleHttpException($ex); // @tips 可自定义处理
		} catch (Throwable $ex) {
			$this->logGuzzleHttpException($ex);
		}
    }

	/**
	 * 记录错误日志
	 *
	 * @param RequestException $ex
	 * @throws ApiException
	 */
	protected function logGuzzleHttpException(Throwable $ex)
	{
		$errContext = $ex->getHandlerContext();
		$errno      = $errContext['errno'] ?? 0;
		$error      = $errno == 28 ? '链接第三方系统超时，请稍候重试。' : 'https请求异常：errcode = ' . $ex->getCode() . ', errmsg = "' . $ex->getMessage() . '"';

		LogHelper::error('guzzle', $error);

		throw new ApiException($error, ApiException::CODE_2028);
	}
}
